﻿/*****************************************
 * This code is mostly written by peter  *
 * Necessary adjustment for Sims3Package *
 * Library is made.                      *
 * ***************************************/
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Reflection;

namespace Sims3Package.Wrapper
{
    /// <summary>
    /// Wrapper for S3SA
    /// </summary>
    public class S3SAResourceWrapper : Wrapper
    {
        /// <summary>
        /// Used in refection for get types
        /// </summary>
        public new static List<UInt32> ResourceTypes { get { return new List<uint>(mResourceTypes); } }
        private new static UInt32[] mResourceTypes = { 0x073FAA07U };

        byte version = 1;
        string gameVersion;
        uint unknown2 = 0x2BC4F79F;
        byte[] md5sum = new byte[64];
        byte[] md5table = new byte[0];
        byte[] md5data = new byte[0];
        byte[] cleardata = new byte[0];

        /// <summary>
        /// Standard constructor
        /// </summary>
        /// <param name="s">Stream that contained in entry, null to represent new resource</param>
        public S3SAResourceWrapper(Stream s)
            : base(s)
        {
            stream = s; Parse();
        }

        private void Parse()
        {
            if (stream == null || stream.Length < 1) return;
            stream.Position = 0;
            BinaryReader br = new BinaryReader(stream);
            version = br.ReadByte();
            if (version > 1)
                gameVersion = System.Text.Encoding.Unicode.GetString(br.ReadBytes(br.ReadInt32() * 2));
            else
                gameVersion = "";
            unknown2 = br.ReadUInt32();
            md5sum = br.ReadBytes(64);
            ushort count = br.ReadUInt16();
            md5table = br.ReadBytes(count * 8);
            md5data = br.ReadBytes(count * 512);
            cleardata = decrypt();
        }

        byte[] decrypt()
        {
            ulong seed = 0;
            for (int i = 0; i < md5table.Length; i += 8) seed += BitConverter.ToUInt64(md5table, i);
            seed = (ulong)(md5table.Length - 1) & seed;

            MemoryStream w = new MemoryStream();
            MemoryStream r = new MemoryStream(md5data);

            for (int i = 0; i < md5table.Length; i += 8)
            {
                byte[] buffer = new byte[512];

                if ((md5table[i] & 1) == 0)
                {
                    r.Read(buffer, 0, buffer.Length);

                    for (int j = 0; j < 512; j++)
                    {
                        byte value = buffer[j];
                        buffer[j] ^= md5table[seed];
                        seed = (ulong)((seed + value) % (ulong)md5table.Length);
                    }
                }

                w.Write(buffer, 0, buffer.Length);
            }

            return w.ToArray();
        }
        /// <summary>
        /// Returns the stream written into entry
        /// </summary>
        /// <returns>Stream.IO.Stream</returns>
        public override Stream UnParse()
        {
            
            MemoryStream ms = new MemoryStream();
            BinaryWriter bw = new BinaryWriter(ms);
            bw.Write(version);
            if (version > 1)
            {
                bw.Write(gameVersion.Length);
                bw.Write(System.Text.Encoding.Unicode.GetBytes(gameVersion));
            }
            bw.Write(unknown2);
            md5table = new byte[(((cleardata.Length & 0x1ff) == 0 ? 0 : 1) + (cleardata.Length >> 9)) << 3];
            md5data = encrypt();
            bw.Write(md5sum);
            bw.Write((ushort)(md5table.Length >> 3));
            bw.Write(md5table);
            bw.Write(md5data);
            ms.Position = 0;
            return ms;
        }

        byte[] encrypt()
        {
            ulong seed = 0;
            for (int i = 0; i < md5table.Length; i += 8) seed += BitConverter.ToUInt64(md5table, i);
            seed = (ulong)(md5table.Length - 1) & seed;

            MemoryStream w = new MemoryStream();
            MemoryStream r = new MemoryStream(cleardata);

            for (int i = 0; i < md5table.Length; i += 8)
            {
                byte[] buffer = new byte[512];
                r.Read(buffer, 0, buffer.Length);

                for (int j = 0; j < 512; j++)
                {
                    buffer[j] ^= md5table[seed];
                    seed = (ulong)((seed + buffer[j]) % (ulong)md5table.Length);
                }

                w.Write(buffer, 0, buffer.Length);
            }

            return w.ToArray();
        }

        public BinaryReader Assembly
        {
            get { return new BinaryReader(new MemoryStream(cleardata)); }
            set
            {
                if (value.BaseStream.CanSeek) { value.BaseStream.Position = 0; cleardata = value.ReadBytes((int)value.BaseStream.Length); }
                else
                {
                    MemoryStream ms = new MemoryStream();
                    byte[] buffer = new byte[1024 * 1024];
                    for (int read = value.BaseStream.Read(buffer, 0, buffer.Length); read > 0; read = value.BaseStream.Read(buffer, 0, buffer.Length))
                        ms.Write(buffer, 0, read);
                    cleardata = ms.ToArray();
                }
            }
        }

        /// <summary>
        /// Used in text preview
        /// </summary>
        public override string Value
        {
            get
            {
                if (cleardata.Length < 2) return string.Empty;
                StringBuilder b = new StringBuilder();
                try
                {
                    Assembly assy = System.Reflection.Assembly.Load(cleardata);
                    b.AppendFormat("Assembly Name: {0}", assy.FullName).AppendLine();
                    
                    // Properties
                    foreach (var p in assy.GetType().GetProperties())
                    {
                        try { b.AppendFormat("{0}: {1}", p.Name, p.GetValue(assy, null)).AppendLine(); }
                        catch (Exception ex) { b.AppendFormat("  {0}: Error reading Value: {1}", p.Name, ex.Message).AppendLine(); }
                    }

                    

                }
                catch (Exception ex)
                {
                    b.AppendLine(ex.InnerException.Message);
                }
                return b.ToString();
            }
        }
    }
}
